
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;

using Derm.Render;

namespace Derm.OpenGL
{
	/// <summary>
	/// OpenGL bindings.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class is partially generated by BindGen. The generation is based on the lastest OpenGL
	/// specifications (.spec files).
	/// </para>
	/// </remarks>
	public partial class Gl : ProcLoader
	{
		#region Constructors

		/// <summary>
		/// Static constructor.
		/// </summary>
		static Gl()
		{
			LinkOpenGLProcImports(typeof(Gl), out sImportMap, out sDelegates);
		}

		#endregion

		#region Extensions Management

		/// <summary>
		/// Check whether current OpenGL implementation supports the extension.
		/// </summary>
		/// <param name="eName">
		/// A <see cref="System.String"/> that specifies the name of the OpenGL extension.
		/// </param>
		/// <returns>
		/// Returns a boolean value that indicates support for the OpenGL extension.
		/// </returns>
		public static bool HasExtension(string eName)
		{
			if (sExtensions == null)
				QueryExtensions();
			return (sExtensions.ContainsKey(eName));
		}

		/// <summary>
		/// Check whether specified OpenGL implementation supports the extension.
		/// </summary>
		/// <param name="versionID">
		/// A <see cref="System.Int32"/> representing the sppecific OpenGL version used to test extension support.
		/// </param>
		/// <param name="eName">
		/// A <see cref="System.String"/> that specifies the name of the OpenGL extension.
		/// </param>
		/// <returns>
		/// Returns a boolean value that indicates support for the OpenGL extension.
		/// </returns>
		public static bool HasExtension(int versionID, string eName)
		{
			if (sExtensionsByVersionID.ContainsKey(versionID) == false)
				QueryExtensions(versionID);
			return (sExtensionsByVersionID[versionID].ContainsKey(eName));
		}

		/// <summary>
		/// Check whether current OpenGL implementation supports the extension.
		/// </summary>
		/// <param name="eName">
		/// A <see cref="System.String"/> that specifies the name of the OpenGL extension.
		/// </param>
		/// <param name="alternatives">
		/// An array of <see cref="System.String"/> that specifies multiple names of OpenGL extensions which are equivalent
		/// to the extension <paramref name="eName"/>.
		/// </param>
		/// <returns>
		/// Returns a boolean value that indicates support for the OpenGL extension.
		/// </returns>
		public static bool HasExtension(string eName, params string[] alternatives)
		{
			bool res = HasExtension(eName);
			
			if (res == false) {
				/* Check for alternatives */
				foreach (string alt in alternatives)
					if (HasExtension(eName) == true)
						return (true);
				
				return (false);
			} else
				return (true);
		}

		/// <summary>
		/// Check whether current OpenGL implementation supports the extension.
		/// </summary>
		/// <param name="versionID">
		/// A <see cref="System.Int32"/> representing the sppecific OpenGL version used to test extension support.
		/// </param>
		/// <param name="eName">
		/// A <see cref="System.String"/> that specifies the name of the OpenGL extension.
		/// </param>
		/// <param name="alternatives">
		/// An array of <see cref="System.String"/> that specifies multiple names of OpenGL extensions which are equivalent
		/// to the extension <paramref name="eName"/>.
		/// </param>
		/// <returns>
		/// Returns a boolean value that indicates support for the OpenGL extension.
		/// </returns>
		public static bool HasExtension(int versionID, string eName, params string[] alternatives)
		{
			bool res = HasExtension(versionID, eName);
			
			if (res == false) {
				/* Check for alternatives */
				foreach (string alt in alternatives)
					if (HasExtension(versionID, eName) == true)
						return (true);
				
				return (false);
			} else
				return (true);
		}

		/// <summary>
		/// Query all OpenGL extensions supported by current implementation.
		/// </summary>
		internal static void QueryExtensions()
		{
			sExtensions = ParseExtensionString(GetString(EXTENSIONS));
		}

		/// <summary>
		/// Query all OpenGL extensions supported by current implementation.
		/// </summary>
		/// <param name="versionID">
		/// A <see cref="System.Int32"/> representing the version of the current OpenGL context. This routine will cache
		/// the returned value of a call <i>GetString(EXTENSIONS)</i>, and index the retult by <paramref name="versionID"/>.
		/// </param>
		internal static void QueryExtensions(int versionID)
		{
			if (sExtensionsByVersionID.ContainsKey(versionID) == false) {
				sExtensionsByVersionID[versionID] = ParseExtensionString(GetString(EXTENSIONS));
			}
		}

		#region Extensions Management

		/// <summary>
		/// Parse an extension string.
		/// </summary>
		/// <param name="exts">
		/// A <see cref="string"/> representing a list of extensions deperated by spaces. This
		/// string is generated using OpenGL routine glGetString or platform specific routines.
		/// </param>
		/// <returns>
		/// It returns a dictionary which maps the extension name with a boolean value indicating
		/// whether an extension is supported or not.
		/// </returns>
		protected static Dictionary<string, bool> ParseExtensionString(string exts)
		{
			Dictionary<string, bool> eDict = new Dictionary<string, bool>();
			string[] extTokens;

			if (String.IsNullOrEmpty(exts))
				return (eDict);		// Empty dictionary

			// Parse available extension
			extTokens = exts.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string e in extTokens)
				eDict.Add(e, true);

			return (eDict);
		}

		#endregion

		/// <summary>
		/// Available extensions, cached (default implementation fallback).
		/// </summary>
		private static Dictionary<string, bool> sExtensions = null;

		/// <summary>
		/// Available extensions, cached (indexed by OpenGL version).
		/// </summary>
		private static Dictionary<int, Dictionary<string, bool>> sExtensionsByVersionID = new Dictionary<int,Dictionary<string,bool>>();

		#endregion

		#region Delegates Management

		/// <summary>
		/// Synchronize OpenGL delegates.
		/// </summary>
		public static void SyncDelegates()
		{
			SynchDelegates(sImportMap, sDelegates);
		}

		/// <summary>
		/// Imported functions delegates.
		/// </summary>
		private static List<FieldInfo> sDelegates = null;

		#endregion

		#region Imports Management

		/// <summary>
		/// Default import library.
		/// </summary>
		private const string Library = "opengl32.dll";
		/// <summary>
		/// Build a string->MethodInfo map to speed up extension loading.
		/// </summary>
		internal static SortedList<string, MethodInfo> sImportMap = null;

		#endregion

		#region Special Overrides

		/// <summary>
		/// Binding for glVertexAttribPointer function.
		/// </summary>
		/// <param name="index">
		/// A <see cref="uint"/>.
		/// </param>
		/// <param name="size">
		/// A <see cref="int"/>.
		/// </param>
		/// <param name="type">
		/// A <see cref="int"/>.
		/// </param>
		/// <param name="normalized">
		/// A <see cref="bool"/>.
		/// </param>
		/// <param name="stride">
		/// A <see cref="Int32"/>.
		/// </param>
		/// <param name="offset">
		/// A <see cref="uint"/>.
		/// </param>
		public static void VertexAttribPointer(uint index, int size, int type, bool normalized, Int32 stride, Int32 offset) {
			Debug.Assert(Delegates.pglVertexAttribPointer != null);
			Delegates.pglVertexAttribPointer(index, size, type, normalized, stride, (IntPtr)offset);
		}

		/// <summary>
		/// Render primitives from array data specifying an start offset.
		/// </summary>
		/// <param name="mode">
		/// Specifies what kind of primitives to render.
		/// Symbolic constants
		/// <see cref="Gl.POINTS"/>,
		/// <see cref="Gl.LINE_STRIP"/>,
		/// <see cref="Gl.LINE_LOOP"/>,
		/// <see cref="Gl.LINES"/>,
		/// <see cref="Gl.LINE_STRIP_ADJACENCY"/>,
		/// <see cref="Gl.LINES_ADJACENCY"/>,
		/// <see cref="Gl.TRIANGLE_STRIP"/>,
		/// <see cref="Gl.TRIANGLE_FAN"/>,
		/// <see cref="Gl.TRIANGLES"/>,
		/// <see cref="Gl.TRIANGLE_STRIP_ADJACENCY"/>,
		/// <see cref="Gl.TRIANGLES_ADJACENCY"/> and <see cref="Gl.PATCHES"/>
		/// are accepted.
		/// </param>
		/// <param name="count">
		/// Specifies the number of elements to be rendered.
		/// </param>
		/// <param name="type">
		/// Specifies the type of the values in <paramref name="indices" />. Must be one of
		/// <see cref="Gl.UNSIGNED_BYTE"/>, <see cref="Gl.UNSIGNED_SHORT"/>, or
		/// <see cref="Gl.UNSIGNED_INT"/>.
		/// </param>
		/// <param name="indices">
		/// Specifies an offset to the location where the indices are stored.
		/// </param>
		public static void DrawElements(int mode, Int32 count, int type, int offset) {
			Debug.Assert(Delegates.pglDrawElements != null);
			Delegates.pglDrawElements(mode, count, type, (IntPtr)offset);
		}

		/// <summary>
		/// Binding for glDrawElementsInstanced function.
		/// </summary>
		/// <param name="mode">
		/// A <see cref="int"/>.
		/// </param>
		/// <param name="count">
		/// A <see cref="Int32"/>.
		/// </param>
		/// <param name="type">
		/// A <see cref="int"/>.
		/// </param>
		/// <param name="offset">
		/// A <see cref="int"/>.
		/// </param>
		/// <param name="primcount">
		/// A <see cref="Int32"/>.
		/// </param>
		public static void DrawElementsInstanced(int mode, Int32 count, int type, int offset, Int32 primcount) {
			Debug.Assert(Delegates.pglDrawElementsInstanced != null);
			Delegates.pglDrawElementsInstanced(mode, count, type, (IntPtr)offset, primcount);
		}

		#endregion

		#region Platform Dependent Abstractions

		public static IntPtr CreateContext(IDeviceContext deviceContext)
		{
			if (deviceContext == null)
				throw new ArgumentNullException("deviceContext");

			switch (Environment.OSVersion.Platform) {
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT: {
						WindowsDeviceContext winDeviceContext = (WindowsDeviceContext)deviceContext;
						return (Wgl.CreateContext(winDeviceContext.DeviceContext));
					}
				case PlatformID.Unix: {
						XServerDeviceContext x11DeviceContext = (XServerDeviceContext)deviceContext;

						IntPtr visual = Glx.GetVisualFromFBConfig(x11DeviceContext.Display, x11DeviceContext.VisualConfig);
						Glx.XVisualInfo info = (Glx.XVisualInfo)Marshal.PtrToStructure(visual, typeof(Glx.XVisualInfo));

						using (Glx.XLock displayLock = new Glx.XLock(x11DeviceContext.Display)) {
							return (Glx.CreateContext(x11DeviceContext.Display, info, IntPtr.Zero, true));
						}
					}
				default:
					throw new NotSupportedException();
			}
		}

		public static IntPtr CreateContextAttrib(IDeviceContext deviceContext, IntPtr sharedContext, int[] attribsList)
		{
			switch (Environment.OSVersion.Platform) {
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT: {
						WindowsDeviceContext winDeviceContext = (WindowsDeviceContext)deviceContext;
						return (Wgl.CreateContextAttribsARB(winDeviceContext.DeviceContext, sharedContext, attribsList));
					}
				case PlatformID.Unix: {
						XServerDeviceContext x11DeviceContext = (XServerDeviceContext)deviceContext;
						return (Glx.CreateContextAttribsARB(x11DeviceContext.Display, x11DeviceContext.VisualConfig, sharedContext, true, ref attribsList));
					}
				default:
					throw new NotSupportedException();
			}
		}

		public static bool MakeContextCurrent(IDeviceContext deviceContext, IntPtr ctx)
		{
			switch (Environment.OSVersion.Platform) {
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT: {
					WindowsDeviceContext winDeviceContext = (WindowsDeviceContext)deviceContext;

					return (Wgl.MakeCurrent(winDeviceContext.DeviceContext, ctx));
				}
				case PlatformID.Unix: {
						XServerDeviceContext x11DeviceContext = (XServerDeviceContext)deviceContext;
						Glx.XVisualInfo visualInfo = new Glx.XVisualInfo();

						visualInfo.visualid = x11DeviceContext.VisualConfig;
						visualInfo.screen = x11DeviceContext.Screen;

						using (Glx.XLock displayLock = new Glx.XLock(x11DeviceContext.Display)) {
							return (Glx.MakeCurrent(x11DeviceContext.Display, x11DeviceContext.WindowHandle.ToInt32(), ctx.ToInt32()));
						}
					}
				default:
					throw new NotSupportedException();
			}
		}

		public static bool DeleteContext(IDeviceContext deviceContext, IntPtr ctx)
		{
			switch (Environment.OSVersion.Platform) {
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT: {
						WindowsDeviceContext winDeviceContext = (WindowsDeviceContext)deviceContext;
						return (Wgl.DeleteContext(ctx));
					}
				case PlatformID.Unix: {
						XServerDeviceContext x11DeviceContext = (XServerDeviceContext)deviceContext;

						using (Glx.XLock displayLock = new Glx.XLock(x11DeviceContext.Display)) {
							Glx.DestroyContext(x11DeviceContext.Display, ctx.ToInt32());
						}
						return (true);
					}
				default:
					throw new NotSupportedException();
			}
		}

		public static void SwapBuffers(IDeviceContext deviceContext)
		{
			switch (Environment.OSVersion.Platform) {
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT: {
						WindowsDeviceContext winDeviceContext = (WindowsDeviceContext)deviceContext;
	
						Wgl.GdiSwapBuffersFast(winDeviceContext.DeviceContext);
					}
					break;
				case PlatformID.Unix: {
						XServerDeviceContext x11DeviceContext = (XServerDeviceContext)deviceContext;

						Glx.SwapBuffers(x11DeviceContext.Display, x11DeviceContext.WindowHandle.ToInt32());
					}
					break;
				default:
					throw new NotSupportedException();
			}
		}

		#endregion
	}
}
